#ifndef MAPPER_HPP_
#define MAPPER_HPP_

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <limits.h>

#include "Logger.hpp"
#include "File.hpp"

const unsigned int pageSize = getpagesize();
const unsigned int chunkSize = 256*4096*50; //50 Mb
const unsigned int maxChunks = INT_MAX;

class Mapper {
public:
	enum Mode {
		MODE_READ,
		MODE_WRITE,
		MODE_RW
	};

	/*
	 * Maps next part of file. Correct size to map must be set before invocation.
  	 */
	static void map(File &file, Mode mode) {
		assert(file.mSize > 0);
		assert(0 == file.offset % pageSize);
		if (file.isMapped) {
			Logger::getInstance().log(Logger::ERROR, "Failed to map file *%s*: file is already mapped.\n", file.path);		
			exit(1);
		}

		if ( ! file.isOpened) {
			Logger::getInstance().log(Logger::ERROR, "Failed to map file *%s*: file is not opened.\n", file.path);		
			exit(1);
		}
		if (mode != MODE_READ) {
			if (-1 == ftruncate(file.fd,file.offset + file.mSize)) {	
				Logger::getInstance().log(Logger::ERROR, "Ftruncate of file *%s* failed.\n", file.path);
				exit(1);
			}
		}

		if (MODE_READ == mode) {
			file.mPtr = (char*)mmap(NULL, file.mSize, PROT_READ, MAP_SHARED, file.fd, file.offset);
		}
		if (MODE_WRITE == mode) {
			file.mPtr = (char*)mmap(NULL, file.mSize, PROT_WRITE, MAP_SHARED, file.fd, file.offset);
		}
		if (MODE_RW == mode) {
			file.mPtr = (char*)mmap(NULL, file.mSize, PROT_READ | PROT_WRITE, MAP_SHARED, file.fd, file.offset);
		}

		if (MAP_FAILED == file.mPtr) {
			Logger::getInstance().log(Logger::ERROR, "Map of file *%s* failed.\n", file.path);
			exit(1);
		}
		
		file.offset += file.mSize;
		file.cPtr = file.mPtr;
		file.isMapped = 1;
	}
	
	static void unmap(File &file) {
		assert(file.isOpened);
		if (file.isMapped == 0) {
			Logger::getInstance().log(Logger::ERROR, "Unmap failed. File *%s* is not mapped.\n", file.path);
			exit(1);
		}
		
		if (-1 == munmap(file.mPtr, file.mSize)) {
			Logger::getInstance().log(Logger::ERROR, "Unmap of file *%s* failed.\n", file.path);
			exit(1);
		}

		file.isMapped = 0;
	}  
};

#endif /* MAPPER_HPP_ */
